package orm

import (
	"context"
	"gitee.com/geektime-geekbang/geektime-go/orm/homework2/internal/errs"
	"reflect"
)

type Updater[T any] struct {
	builder
	db      *DB
	assigns []Assignable
	val     *T
	where   []Predicate
}

func NewUpdater[T any](db *DB) *Updater[T] {
	return &Updater[T]{
		db: db,
		builder: builder{
			dialect: db.dialect,
			quoter:  db.dialect.quoter(),
		},
	}
}

func (u *Updater[T]) Update(t *T) *Updater[T] {
	u.val = t
	return u
}

func (u *Updater[T]) Set(assigns ...Assignable) *Updater[T] {
	u.assigns = assigns
	return u
}

func (u *Updater[T]) Build() (*Query, error) {
	if len(u.assigns) == 0 {
		return nil, errs.ErrNoUpdatedColumns
	}
	t := new(T)
	model, err := u.db.r.Get(t)
	if err != nil {
		return nil, err
	}
	u.model = model
	u.sb.WriteString("UPDATE ")
	u.quote(u.model.TableName)
	u.sb.WriteString(" SET ")
	for i, assign := range u.assigns {
		if i > 0 {
			u.sb.WriteString(",")
		}
		switch assignVal := assign.(type) {
		case Assignment:
			fd, ok := u.model.FieldMap[assignVal.column]
			if !ok {
				return nil, errs.NewErrUnknownField(assignVal.column)
			}
			u.quote(fd.ColName)
			u.sb.WriteString("=")
			err = u.buildExpression(assignVal.val)
			if err != nil {
				return nil, err
			}
		case Column:
			fd, ok := u.model.FieldMap[assignVal.name]
			if !ok {
				return nil, errs.NewErrUnknownField(assignVal.name)
			}
			u.quote(fd.ColName)
			u.sb.WriteString("=?")
			rVal := reflect.ValueOf(u.val).Elem()
			fdVal := rVal.Field(fd.Index)
			u.args = append(u.args, fdVal.Interface())
		case RawExpr:
			u.raw(assignVal)
		}
	}
	if len(u.where) > 0 {
		u.sb.WriteString(" WHERE ")
		err = u.buildPredicates(u.where)
		if err != nil {
			return nil, err
		}
	}
	u.sb.WriteString(";")
	return &Query{
		SQL:  u.sb.String(),
		Args: u.args,
	}, nil
}

func (u *Updater[T]) Where(ps ...Predicate) *Updater[T] {
	u.where = ps
	return u
}

func (u *Updater[T]) Exec(ctx context.Context) Result {
	return Result{}
}

// AssignNotZeroColumns 更新非零值
func AssignNotZeroColumns(entity interface{}) []Assignable {
	val := reflect.ValueOf(entity)
	typ := reflect.TypeOf(entity)
	if val.Kind() == reflect.Ptr {
		val = val.Elem()
		typ = typ.Elem()
	}
	if val.Kind() != reflect.Struct {
		return []Assignable{}
	}
	numField := typ.NumField()
	assigns := make([]Assignable, 0, numField)
	for i := 0; i < numField; i++ {
		if val.Field(i).IsZero() {
			continue
		}
		assign := Assignment{
			column: typ.Field(i).Name,
			val: value{
				val: val.Field(i).Interface(),
			},
		}
		assigns = append(assigns, assign)
	}
	return assigns
}
